//
// Created by AnTler on 2018/11/30.
//

#include "ImageUtils.h"

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

#include <android/bitmap.h>

#include <sstream>

#include "LuaJavaHelper.h"

#include "JNIDef.h"

static jmethodID methodID_Rect_init_IIIIV = nullptr;

static uint32_t
vinfoToPixelFormat(const fb_var_screeninfo &vinfo, uint32_t *bytespp, AndroidBitmapFormat *f) {
    switch (vinfo.bits_per_pixel) {
        case 16:
            *f = ANDROID_BITMAP_FORMAT_RGB_565;
            *bytespp = 2;
            break;
        case 24:
            *f = ANDROID_BITMAP_FORMAT_RGBA_8888;
            *bytespp = 3;
            break;
        case 32:
            *f = ANDROID_BITMAP_FORMAT_RGBA_8888;
            *bytespp = 4;
            break;
        default:
            return BAD_VALUE;
    }
    return NO_ERROR;
}

namespace ImageUtils {

    jobject fromScreen(const Rect &rect) {
        JNIEnv *env = LuaJavaHelper::getInstance()->getEnv();
        if (!env) {
            return nullptr;
        }

        jobject jBitmap = nullptr;

        const char *fbpath = "/dev/graphics/fb0";
        int fb = open(fbpath, O_RDONLY);
        if (fb < 0) {
            ScopedLocalRef<jclass> localClassRect(env, env->FindClass("android/graphics/Rect"));
            if (localClassRect) {
                if (methodID_Rect_init_IIIIV == nullptr) {
                    methodID_Rect_init_IIIIV = env->GetMethodID(localClassRect, "<init>",
                                                                "(IIII)V");
                }

                if (methodID_Rect_init_IIIIV) {
                    jobject objectRect = env->NewObject(localClassRect,
                                                        methodID_Rect_init_IIIIV,
                                                        rect.x, rect.y,
                                                        rect.x + rect.width,
                                                        rect.y + rect.height);

                    ScopedLocalRef<jobject> localObjectRect(env, objectRect);
                    if (localObjectRect) {
                        jBitmap = LuaJavaHelper::callStaticObjectMethod(env,
                                                                        CLASS_NAME_IMAGEUTILS,
                                                                        METHOD_NAME_IMAGEUTILS_FROMSCREEN,
                                                                        METHOD_SIG_IMAGEUTILS_FROMSCREEN,
                                                                        (jobject) localObjectRect);
                    }
                }
            }
        } else {
            struct fb_var_screeninfo vinfo;
            if (ioctl(fb, FBIOGET_VSCREENINFO, &vinfo) == 0) {
                uint32_t bytespp;
                AndroidBitmapFormat f;
                if (vinfoToPixelFormat(vinfo, &bytespp, &f) == NO_ERROR) {
                    size_t offset = (vinfo.xoffset + vinfo.yoffset * vinfo.xres) * bytespp;
                    uint32_t w = vinfo.xres;
                    uint32_t h = vinfo.yres;
                    uint32_t s = vinfo.xres;
                    size_t size = w * h * bytespp;
                    ssize_t mapsize = offset + size;
                    void const *mapbase = mmap(0, mapsize, PROT_READ, MAP_PRIVATE, fb, 0);
                    if (mapbase != MAP_FAILED) {
                        void const *base = (void const *) ((char const *) mapbase + offset);
                        if (base) {
                            ScopedLocalRef<jbyteArray> jpixels(env, env->NewByteArray(size));
                            if (jpixels) {
                                env->SetByteArrayRegion(jpixels, 0, size, (const jbyte *) base);

                                jBitmap = LuaJavaHelper::callStaticObjectMethod(env,
                                                                                "android/graphics/BitmapFactory",
                                                                                "decodeByteArray",
                                                                                "([BII)Landroid/graphics/Bitmap;",
                                                                                (jbyteArray) jpixels,
                                                                                0, size);
                            }
                        }

                        munmap((void *) mapbase, mapsize);
                    }
                }
            }

            close(fb);
        }

        return jBitmap;
    }

    jobject fromFile(const std::string &filePath) {
        JNIEnv *env = LuaJavaHelper::getInstance()->getEnv();
        if (!env) {
            return nullptr;
        }

        jobject jBitmap = nullptr;

        ScopedLocalRef<jstring> jfilePath(env, env->NewStringUTF(filePath.c_str()));
        if (jfilePath) {
            jBitmap = LuaJavaHelper::callStaticObjectMethod(env,
                                                            CLASS_NAME_IMAGEUTILS,
                                                            METHOD_NAME_IMAGEUTILS_FROMFILE,
                                                            METHOD_SIG_IMAGEUTILS_FROMFILE,
                                                            (jstring) jfilePath);
        }
        return jBitmap;
    }

    bool saveToFile(jobject jBitmap, const std::string &filePath, int quality) {
        JNIEnv *env = LuaJavaHelper::getInstance()->getEnv();
        if (!env) {
            return false;
        }

        ScopedLocalRef<jstring> jfilePath(env, env->NewStringUTF(filePath.c_str()));
        if (jfilePath) {
            return LuaJavaHelper::callStaticBooleanMethod(env,
                                                          CLASS_NAME_IMAGEUTILS,
                                                          METHOD_NAME_IMAGEUTILS_SAVETOFILE,
                                                          METHOD_SIG_IMAGEUTILS_SAVETOFILE,
                                                          jBitmap, (jstring) jfilePath, quality);
        }
        return false;
    }

    jobject rotation(jobject jBitmap, float degree) {
        JNIEnv *env = LuaJavaHelper::getInstance()->getEnv();
        if (!env) {
            return nullptr;
        }

        return LuaJavaHelper::callStaticObjectMethod(env,
                                                     CLASS_NAME_IMAGEUTILS,
                                                     METHOD_NAME_IMAGEUTILS_ROTATION,
                                                     METHOD_SIG_IMAGEUTILS_ROTATION,
                                                     jBitmap, degree);
    }

    jobject clip(jobject jBitmap, const Rect &rect) {
        JNIEnv *env = LuaJavaHelper::getInstance()->getEnv();
        if (!env) {
            return nullptr;
        }

        ScopedLocalRef<jclass> localClassRect(env, env->FindClass("android/graphics/Rect"));
        if (localClassRect) {
            if (methodID_Rect_init_IIIIV == nullptr) {
                methodID_Rect_init_IIIIV = env->GetMethodID(localClassRect, "<init>",
                                                            "(IIII)V");
            }

            if (methodID_Rect_init_IIIIV) {
                jobject objectRect = env->NewObject(localClassRect,
                                                    methodID_Rect_init_IIIIV,
                                                    rect.x, rect.y,
                                                    rect.x + rect.width,
                                                    rect.y + rect.height);

                ScopedLocalRef<jobject> localObjectRect(env, objectRect);
                if (localObjectRect) {
                    return LuaJavaHelper::callStaticObjectMethod(env,
                                                                 CLASS_NAME_IMAGEUTILS,
                                                                 METHOD_NAME_IMAGEUTILS_CLIP,
                                                                 METHOD_SIG_IMAGEUTILS_CLIP,
                                                                 jBitmap,
                                                                 (jobject) localObjectRect);
                }
            }
        }
        return nullptr;
    }

    jobject *binarize() {
        return nullptr;
    }
};